#include "date_time.h"
#include "str.h"
#include "converter.h"
#include "smart_stream.h"
#include "useful_functions.h"

void date_time::split_values(const string &input, unsigned short output[3], char separator) {
  output[0] = output[1] = output[2] = 0;
  list<string> l1;
  str::split(input, string(1, separator), l1);

  if (l1.size() == 3) {
    smart_stream ss;
    unsigned short counter = 0;

    for (auto it = l1.cbegin(); it != l1.cend(); ++it) {
      ss.set_buffer(*it);
      ss >> output[counter];
      ++counter;
    } // for
  } // if
}

void date_time::process_date_and_time(const string &input, char date_separator, char time_separator) {
  list<string> l1;
  str::split(input, " ", l1);

  if (l1.size() == 2) {
    process_date(*l1.begin(), date_separator);
    process_time(*l1.rbegin(), time_separator);
  } // if
}

void date_time::process_date(const string &input, char date_separator) {
  unsigned short values[3] = { 0, 0, 0 };
  split_values(input, values, date_separator);
  this->set_date(values[0], values[1], values[2]);
}

void date_time::process_time(const string &input, char time_separator) {
  unsigned short values[3] = { 0, 0, 0 };
  split_values(input, values, time_separator);
  this->set_time(values[0], values[1], values[2], 0);
}

unsigned long long date_time::get_total_time(time_unit unit) const {
  switch (unit) {
    case millisecond:
      return (time_value.hour * 3600000) + (time_value.minute * 60000) + (time_value.second * 1000) + time_value.millisecond;

    case second:
      return (time_value.hour * 3600) + (time_value.minute * 60) + time_value.second;

    case minute:
      return (time_value.hour * 60) + time_value.minute;

    case hour:
      return time_value.hour;

  } // switch

  return 0;
}

const general_date &date_time::get_date() const {
  return this->date_value;
}

void date_time::set_date(unsigned short year, unsigned short month, unsigned short day) {
  general_date gd;
  gd.year = year;
  gd.month = month;
  gd.day = day;

  this->set_date(gd);
}

void date_time::set_date(const general_date &gd) {
  this->date_value = gd;
}

const general_time &date_time::get_time() const {
  return this->time_value;
}

void date_time::set_time(unsigned short hour, unsigned short minute, unsigned short second, unsigned short millisecond) {
  general_time gt;
  gt.hour = hour;
  gt.minute = minute;
  gt.second = second;
  gt.millisecond = millisecond;

  this->set_time(gt);
}

void date_time::set_time(const general_time &gt) {
  this->time_value = gt;
}

void date_time::set_date_time(const string &input, char date_separator, char time_separator) {
  this->set_date(0, 0, 0);
  this->set_time(0, 0, 0, 0);

  // we should check the input strictly
  if (input.length() == strlen("YYYY-MM-DD hh:mm:ss") && std::count_if(input.begin(), input.end(), is_digit) == 14 && input[4] == date_separator && input[7] == date_separator && input[13] == time_separator && input[16] == time_separator)
    process_date_and_time(input, date_separator, time_separator);
  else if (input.length() == strlen("YYYY-MM-DD") && std::count_if(input.begin(), input.end(), is_digit) == 8 && input[4] == date_separator && input[7] == date_separator)
    process_date(input, date_separator);
  else if (input.length() == strlen("hh:mm:ss") && std::count_if(input.begin(), input.end(), is_digit) == 6 && input[2] == time_separator && input[5] == time_separator)
    process_time(input, time_separator);
}

string date_time::to_string(string format) const {
  string str_cnv;

  converter::to_string(this->date_value.year, str_cnv);
  str::prepend(str_cnv, '0', 4);
  converter::replace_all(format, "%Y", str_cnv, format);

  converter::to_string(this->date_value.month, str_cnv);
  str::prepend(str_cnv, '0', 2);
  converter::replace_all(format, "%M", str_cnv, format);

  converter::to_string(this->date_value.day, str_cnv);
  str::prepend(str_cnv, '0', 2);
  converter::replace_all(format, "%D", str_cnv, format);

  // do the millisecond first
  converter::to_string(this->time_value.millisecond, str_cnv);
  str::prepend(str_cnv, '0', 3);
  converter::replace_all(format, "%ms", str_cnv, format);

  converter::to_string(this->time_value.hour, str_cnv);
  str::prepend(str_cnv, '0', 2);
  converter::replace_all(format, "%h", str_cnv, format);

  converter::to_string(this->time_value.minute, str_cnv);
  str::prepend(str_cnv, '0', 2);
  converter::replace_all(format, "%m", str_cnv, format);

  converter::to_string(this->time_value.second, str_cnv);
  str::prepend(str_cnv, '0', 2);
  converter::replace_all(format, "%s", str_cnv, format);
  return format;
}

date_time date_time::operator+(const date_time &t) const {
  date_time result = *this;
  result.date_value.year += t.date_value.year;
  result.date_value.month += t.date_value.month;
  result.date_value.day += t.date_value.day;

  result.time_value.hour += t.time_value.hour;
  result.time_value.minute += t.time_value.minute;
  result.time_value.second += t.time_value.second;
  result.time_value.millisecond += t.time_value.millisecond;
  return result;
}

date_time date_time::operator-(const date_time &t) const {
  date_time result = *this;
  result.date_value.year -= t.date_value.year;
  result.date_value.month -= t.date_value.month;
  result.date_value.day -= t.date_value.day;

  result.time_value.hour -= t.time_value.hour;
  result.time_value.minute -= t.time_value.minute;
  result.time_value.second -= t.time_value.second;
  result.time_value.millisecond -= t.time_value.millisecond;
  return result;
}

date_time &date_time::operator=(const date_time &t) {
  if (this == &t)
    return *this;

  this->date_value = t.date_value;
  this->time_value = t.time_value;
  return *this;
}

bool date_time::operator>(const date_time &t) const {
  if (this->date_value > t.date_value)
    return true;
  else if (this->date_value == t.date_value)
    return (this->time_value > t.time_value);

  return false;
}

bool date_time::operator>=(const date_time &t) const {
  return (*this > t || *this == t);
}

bool date_time::operator<(const date_time &t) const {
  return !(this->date_value >= t.date_value);
}

bool date_time::operator<=(const date_time &t) const {
  return (*this < t || *this == t);
}

bool date_time::operator==(const date_time &t) const {
  return (this->date_value == t.date_value && this->time_value == t.time_value);
}

void date_time::set_to_current_date_and_time() {
  time_t now;
  time(&now); // current date & time
  if (now == -1)
    return;

  struct tm *timeinfo = 0;
  timeinfo = localtime(&now);
  if (timeinfo == 0)
    return;

  this->time_value.millisecond = 0; // not supported here
  this->time_value.second = timeinfo->tm_sec;
  this->time_value.minute = timeinfo->tm_min;
  this->time_value.hour = timeinfo->tm_hour;

  this->date_value.day = timeinfo->tm_mday;
  this->date_value.month = timeinfo->tm_mon + 1;
  this->date_value.year = timeinfo->tm_year + 1900;
}

date_time::date_time() {
  set_to_current_date_and_time();
}

date_time::date_time(const date_time &dt) : date_value(dt.date_value), time_value(dt.time_value) {
  // nothing to do
}

date_time::date_time(unsigned short year, unsigned short month, unsigned short day, unsigned short hour, unsigned short minute, unsigned short second, unsigned short millisecond) {
  this->date_value.year = year;
  this->date_value.month = month;
  this->date_value.day = day;

  this->time_value.hour = hour;
  this->time_value.minute = minute;
  this->time_value.second = second;
  this->time_value.millisecond = millisecond;
}

date_time::~date_time() {
  // nothing to do
}
